package com.atguigu.tingshu.common.login;

import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * 对所有业务微服务模块中com.atguigu.tingshu.*.api包下所有使用@GuiGuLogin注解方法所在类进行增强
 *
 * @author: atguigu
 * @create: 2024-08-06 10:39
 */
@Slf4j
@Aspect
@Component
public class GuiguLoginAspect {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 进行验证用户登录状态环绕通知逻辑
     *
     * @param pjp        切入点对象
     * @param guiGuLogin 方法使用使用注解对象
     * @return
     * @throws Throwable
     */
    @Around("execution(* com.atguigu.tingshu.*.api.*.*(..)) && @annotation(guiGuLogin)")
    public Object doBasicProfiling(ProceedingJoinPoint pjp, GuiGuLogin guiGuLogin) throws Throwable {
        //1.前置逻辑
        log.info("[认证注解切面]前置逻辑...");
        //1.1 获取请求对象；获取请求头名称为token的用户令牌
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) requestAttributes;
        HttpServletRequest request = sra.getRequest();
        String token = request.getHeader("token");
        //1.2 将token作为Key查询Redis中用户信息
        //1.2.1 构建登录成功存入Redis用户令牌Key查询 形式：user:login:token值
        String loginKey = RedisConstant.USER_LOGIN_KEY_PREFIX + token;
        //1.2.2 获取存在Redis中用户信息UserInfoVo
        UserInfoVo userInfoVo = (UserInfoVo) redisTemplate.opsForValue().get(loginKey);


        //1.3 如果认证注解对象属性required=true且用户信息为空抛出异常：状态码=208 引导用户登录
        if (guiGuLogin.required() && userInfoVo == null) {
            throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
        }
        //1.4 如果用户信息有值，将用户ID存入ThreadLocal中
        if (userInfoVo != null) {
            AuthContextHolder.setUserId(userInfoVo.getId());
        }
        //2.执行目标方法(controller->service-mapper)
        Object retVal = pjp.proceed();
        //3.后置逻辑
        log.info("[认证注解切面]后置逻辑...");
        //3.1 手动清理ThreadLocal避免出现使用不当导致内存泄漏，不断内存泄漏导致内存溢出
        AuthContextHolder.removeUserId();
        return retVal;
    }
}
